package com.gillion.grule.client.components;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.gillion.grule.client.core.Component;
import com.gillion.grule.client.core.Context;
import com.gillion.grule.client.core.DubboConfig;
import com.gillion.grule.client.core.ExecuteComponent;
import com.gillion.grule.client.exception.ExecutorException;
import com.gillion.grule.client.utils.ContextHolder;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.mvel2.MVEL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;


@Component("Dubbo")
public class DubboComponent
        extends ExecuteComponent {
    private static final Logger LOGGER = LoggerFactory.getLogger(DubboComponent.class);
    private static Map<String, Method> dubboCache = Maps.newConcurrentMap();


    public <T> T execute(String express, Context context) {
        LOGGER.info("规则引擎 调用dobbu配置：" + express);
        JSONObject jSONObject = JSON.parseObject(express);
        List<Object> params = (List<Object>) jSONObject.get("params");
        String className = "";
        String methodName = "";
        String paramsConfig = "";
        List<DubboConfig> dubboConfigs = context.getDubboConfig();
        for (DubboConfig dubboConfig : dubboConfigs) {
            if (jSONObject.get("dubboName").equals(dubboConfig.getDubboName())) {
                className = dubboConfig.getClassName();
                methodName = dubboConfig.getMethodName();
                paramsConfig = dubboConfig.getParams();
                break;
            }
        }
        JSONArray jSONArray = JSON.parseArray(paramsConfig);
        if (paramsConfig.isEmpty() || className.isEmpty() || methodName.isEmpty()) {
            LOGGER.error("Dubbo组件配置错误，无法找对应的配置！");
            throw new ExecutorException(500001, "Dubbo组件配置错误，无法找对应的配置！");
        }
        if (jSONArray.size() != params.size() || className.isEmpty() || methodName.isEmpty()) {
            LOGGER.error("Dubbo组件入参配置错误！");
            return null;
        }
        Method method = dubboCache.get(className + methodName + jSONArray.toString());


        if (method == null) {
            method = getMethod(className, methodName,  jSONArray.toJavaList(String.class));
        }
        LOGGER.info("调用方法参数：类名" + className + "方法名:" + methodName);
        Class<?>[] parameterTypes = method.getParameterTypes();
        if (params.size() != parameterTypes.length) {
            LOGGER.info("dubbo组件调用失败，入参和方法的入参个数不同！");
            throw new ExecutorException(5000000, "dubbo组件调用失败，入参和方法的入参个数不同");
        }
        List<Object> param = Lists.newArrayList();
        for (int i = 0; i < params.size(); i++) {
            Object obj = params.get(i);
            if (obj instanceof String && jSONObject.get("type") != null) {
                Object o;
                if (params.get(i).toString().indexOf("'") > -1) {
                    o = params.get(i).toString();
                } else {
                    o = MVEL.eval("'" + params.get(i).toString() + "'", context);
                }
                if (o instanceof Map || o instanceof List) {
                    param.add(mapToObject((Map<String, Object>) o, parameterTypes[i]));
                } else {
                    param.add(objectToClass(o, parameterTypes[i]));
                }
            } else {
                Object o = null;
                if (params.get(i) != null) {
                    o = MVEL.eval(params.get(i).toString(), context);
                }
                if (o instanceof Map || o instanceof List) {
                    param.add(mapToObject((Map<String, Object>) o, parameterTypes[i]));
                } else {
                    param.add(objectToClass(o, parameterTypes[i]));
                }
            }
        }

        Object result = null;
        try {
            Object object = ContextHolder.getBean(className);
            if (object == null) {
                object = Class.forName(className).newInstance();
            }
            LOGGER.info("dubbo 服务调用开始：");
            result = method.invoke(object, param.toArray());
            LOGGER.info("dubbo 服务返回结果：" + result);
        } catch (Exception e) {
            LOGGER.error("Dubbo组件方法调用失败！", e);
        }
        return (T) result;
    }

    private Method getMethod(String className, String methodName, List<String> paramsConfigList) {
        LOGGER.info("调用方法参数：类名" + className + "方法名：+" + paramsConfigList + ",参数：+" + paramsConfigList);
        Method method = null;
        try {
            Object object = ContextHolder.getBean(className);
            if (object == null) {
                object = Class.forName(className).newInstance();
            }
            Class<?> clazz = object.getClass();
            List<Class<?>> classList = new ArrayList<>();
            for (String str : paramsConfigList) {
                classList.add(Class.forName(str));
            }
            Class[] classArray = new Class[classList.size()];
            for (int i = 0; i < classArray.length; i++) {
                classArray[i] = classList.get(i);
            }
            method = clazz.getDeclaredMethod(methodName, classArray);
            dubboCache.put(className + methodName + paramsConfigList.toString(), method);
        } catch (Exception e) {
            LOGGER.error("dubbo组件反射执行失败！", e);
        }
        return method;
    }

    private Object mapToObject(Map<String, Object> map, Class<?> clazz) {
        if (map == null) {
            return null;
        }
        for (String key : map.keySet()) {
            if ("".equals(map.get(key))) {
                map.put(key, null);
            }
        }
        Object obj = null;
        try {
            obj = clazz.newInstance();

            BeanUtils.copyProperties(obj, map);
        } catch (Exception e) {
            LOGGER.error("dubbo对象转换失败！", e);
        }
        return obj;
    }

    private Object objectToClass(Object value, Class<?> clazz) {
        if (value == null) {
            return null;
        }
        Object obj = value;
        if (clazz.getName().equals("java.lang.Integer")) {
            obj = Integer.valueOf(value.toString());
        } else if (clazz.getName().equals("java.lang.Double")) {
            obj = Double.valueOf(value.toString());
        } else if (clazz.getName().equals("java.lang.Long")) {
            obj = Long.valueOf(value.toString());
        } else if (clazz.getName().equals("java.lang.Float")) {
            obj = Float.valueOf(value.toString());
        } else if (clazz.getName().equals("java.lang.String")) {
            obj = obj.toString();
        } else if (clazz.getName().equals("java.util.Date")) {
            try {
                obj = new Date(Long.valueOf(value.toString()).longValue());
            } catch (ExecutorException e) {
                obj = value;
            }
        }
        return obj;
    }
}